package com.gitee.fastmybatis.core.support.plugin;


import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.PreparedStatement;
import java.util.Properties;

/**
 * SQL美化插件
 */
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class SqlFormatterPlugin implements Interceptor {
    protected static final Log LOG = LogFactory.getLog(SqlFormatterPlugin.class);

    private final SqlFormatter sqlFormatter = new SqlFormatter();

    private Properties properties;

    /**
     * 打印SQL末尾是否自动添加结束符（;）
     */
    private boolean appendDelimiter;

    /**
     * 结束符
     */
    private String delimiter;

    /**
     * 是否使用Druid
     */
    private boolean useDruid;

    private String format;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            return invocation.proceed();
        } finally {
            if (canPrint()) {
                printSql(invocation);
            }
        }
    }

    protected boolean canPrint() {
        return LOG.isDebugEnabled();
    }

    protected void printSql(Invocation invocation) throws NoSuchFieldException, IllegalAccessException {
        PreparedStatement statement = (PreparedStatement) invocation.getArgs()[0];
        if (Proxy.isProxyClass(statement.getClass())) {
            InvocationHandler handler = Proxy.getInvocationHandler(statement);
            if (handler.getClass().getName().endsWith(".PreparedStatementLogger")) {
                Field field = handler.getClass().getDeclaredField("statement");
                field.setAccessible(true);
                statement = (PreparedStatement) field.get(handler);
                //原文参考：https://www.zhangshengrong.com/p/OgN5DgLDan/
                //以下应该是使用了 druid 原因 ，没有使用的话下面几行代码请注释掉
                if (isUseDruid()) {
                    Field stmt = statement.getClass().getDeclaredField("stmt");
                    stmt.setAccessible(true);
                    statement = (PreparedStatement) stmt.get(statement);
                    Field clientStatement = statement.getClass().getDeclaredField("statement");
                    clientStatement.setAccessible(true);
                    statement = (PreparedStatement) clientStatement.get(statement);
                }
            }
        }
        String statementSql = statement.toString();
        String sql = statementSql.substring(statementSql.indexOf(":") + 1);
        String endMark = getDelimiter();
        if (isAppendDelimiter() && !sql.endsWith(endMark)) {
            sql = sql + endMark;
        }
        String formatSql = getSqlFormatter().format(sql);
        LOG.debug(String.format(this.format, formatSql));
    }

    public SqlFormatter getSqlFormatter() {
        return sqlFormatter;
    }


    public boolean isUseDruid() {
        return useDruid;
    }


    public String getDelimiter() {
        return delimiter;
    }


    public boolean isAppendDelimiter() {
        return appendDelimiter;
    }

    public Properties getProperties() {
        return properties;
    }

    public void setAppendDelimiter(boolean appendDelimiter) {
        this.appendDelimiter = appendDelimiter;
    }

    public void setDelimiter(String delimiter) {
        this.delimiter = delimiter;
    }

    public void setUseDruid(boolean useDruid) {
        this.useDruid = useDruid;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
        this.appendDelimiter = Boolean.parseBoolean(properties.getProperty("appendDelimiter", "true"));
        this.useDruid = Boolean.parseBoolean(properties.getProperty("useDruid", "false"));
        this.delimiter = properties.getProperty("delimiter", ";");
        this.format = properties.getProperty("format", "\n====== SQL ======\n%s").replace("\\n", "\n");
    }
}
